﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;

namespace MultiWallpaperManager
{
	public partial class MultiWallpaper : Form
	{
		private static string[] ImageExtensions = { ".jpg", ".gif", ".bmp", "png", "jpeg" };
		private const int DefaultTimeInterval = 10;

		private WallpaperChanger wallpaperChanger;

		private MyRectangle entireRect;			// system virtual screen rect
		private ScreenInformation[] screens;
		private Point drawOrigin;
		private int selectedScreenIndex;

		private List<ImageItem> imageItemList = new List<ImageItem>();	// all images in specific folder
		private bool winStartup = false;

		public MultiWallpaper()
		{
			InitializeComponent();
		}

		public ScreenInformation CurrentScreen
		{
			get { return screens[selectedScreenIndex]; }
		}

		private void MultiWallpaper_Load(object sender, EventArgs e)
		{
			Init();

			// check winstartup
			if(startupCheckBox.Checked)
			{
				string[] args = Environment.GetCommandLineArgs();

				if (args.Length == 2 && args[1] == WindowsStartup.StartUpOptionString)
				{
					// hide form
					winStartup = true;
				}
			}

			// check change when run
			if (changeWhenRunCheckBox.Checked)
			{
				startButton_Click(null, null);
			}
		}

		private void MultiWallpaper_FormClosing(object sender, FormClosingEventArgs e)
		{
			if (e.CloseReason == CloseReason.UserClosing)
			{
				if (DialogResult.Yes != SafeMessageBox(Resource.ProgramExitConfirm, Resource.MessageBoxTitle, MessageBoxButtons.YesNo))
				{
					e.Cancel = true;
				}
			}
		}

		private void MultiWallpaper_FormClosed(object sender, FormClosedEventArgs e)
		{
			SaveConfig();
		}

		private void ShowForm()
		{
			Show();

			WindowState = FormWindowState.Normal;
		}

		private void MultiWallpaper_Shown(object sender, EventArgs e)
		{
			if (winStartup)
			{
				winStartup = false;

				Hide();
			}
		}

		private void Init()
		{
			MakeScreenInformation();

			wallpaperChanger = new WallpaperChanger(screens, entireRect);

			ResetItems();

			LoadConfig();

			// sync combobox and preview
			screenComboBox.SelectedIndex = selectedScreenIndex;
		}

		private void ResetItems()
		{
			foreach (ScreenInformation screen in screens)
			{
				screen.selectedThumbnail = null;
				screen.selectedImageIndex = -1;
			}

			imageSizeLabel.Text = Resource.ImageSizeLabelDefault;
		}

		private void MakeScreenInformation()
		{
			// get bounds
			Screen[] allScreens = Screen.AllScreens;

			screens = new ScreenInformation[allScreens.Length];

			for (int i = 0; i < allScreens.Length; i++)
			{
				screens[i] = new ScreenInformation();

				screens[i].bounds = allScreens[i].Bounds;
				screens[i].deviceName = allScreens[i].DeviceName;
				screens[i].primary = allScreens[i].Primary;
			}

			MakeScreenPreviews(allScreens);

			InitScreenComboBoxes();

			// set index
			selectedScreenIndex = 0;

			// redraw
			previewBox.Invalidate();
		}

		private void MakeScreenPreviews(Screen[] allScreens)
		{
			int previewWidth = previewBox.Width;
			int previewHeight = previewBox.Height;
			float scaleFactor;
			entireRect = new MyRectangle();

			foreach (Screen screen in allScreens)
			{
				Rectangle screenRect = screen.Bounds;

				if (screenRect.Left < entireRect.left)
					entireRect.left = screenRect.Left;

				if (screenRect.Top < entireRect.top)
					entireRect.top = screenRect.Top;

				if (screenRect.Right > entireRect.right)
					entireRect.right = screenRect.Right;

				if (screenRect.Bottom > entireRect.bottom)
					entireRect.bottom = screenRect.Bottom;
			}

			scaleFactor = Math.Min((float)previewWidth / entireRect.Width, (float)previewHeight / entireRect.Height);

			Point origin = new Point();

			origin.X = (int)((previewWidth - entireRect.Width * scaleFactor) / 2);
			origin.Y = (int)((previewHeight - entireRect.Height * scaleFactor) / 2);

			drawOrigin = origin;

			Point offset = new Point(-entireRect.left, -entireRect.top);

			foreach (ScreenInformation si in screens)
			{
				Rectangle screenBounds = si.bounds;

				screenBounds.Offset(offset);

				si.previewRect = new Rectangle((int)(screenBounds.X * scaleFactor), (int)(screenBounds.Y * scaleFactor),
												(int)(screenBounds.Width * scaleFactor), (int)(screenBounds.Height * scaleFactor));
			}
		}

		private void InitScreenComboBoxes()
		{
			screenComboBox.Items.Clear();

			foreach (ScreenInformation si in screens)
			{
				string adapterString;
				string monitorString;

				WindowsAPI.GetDeviceAndMonitorName(si.deviceName, out adapterString, out monitorString);

				si.displayName = string.Format("{0} ({1})", monitorString, adapterString);

				if (si.primary)
					si.displayName += " - Primary";

				screenComboBox.Items.Add(si.displayName);
			}
		}

		private void folderBrowseButton_Click(object sender, EventArgs e)
		{
			DialogResult result = folderBrowserDialog.ShowDialog();

			if (result == DialogResult.OK)
			{
				string path = folderBrowserDialog.SelectedPath;

				OpenImageFolder(path);

				previewBox.Invalidate();
			}
		}

		private void imageListBox_SelectedIndexChanged(object sender, EventArgs e)
		{
			if (imageListBox.SelectedIndex == -1)
				return;

			// set image index
			CurrentScreen.selectedImageIndex = imageListBox.SelectedIndex;

			// set image to selected preview screen
			ImageItem selectedItem = imageListBox.SelectedItem as ImageItem;

			CurrentScreen.selectedThumbnail = selectedItem.Thumbnail;
			CurrentScreen.selectedImageFilePath = selectedItem.FilePath;

			// update image size
			UpdateImageSizeLabel(selectedItem.Width, selectedItem.Height);

			previewBox.Invalidate();
		}

		private bool CheckValidWallpaperSelected(bool notifyOnError)
		{
			// check all images are selected
			foreach (ScreenInformation screen in screens)
			{
				if (screen.random == false && screen.selectedImageIndex == -1)
				{
					if(notifyOnError)
						SafeMessageBox(Resource.SelectImage, Resource.MessageBoxTitle);

					return false;
				}

				if (screen.random && screen.imageItemList.Count == 0)
				{
					if(notifyOnError)
						SafeMessageBox(string.Format(Resource.NoAvailableImageInScreen, screen.displayName),
										Resource.MessageBoxTitle);

					return false;
				}
			}

			return true;
		}

		private void applyNowButton_Click(object sender, EventArgs e)
		{
			if (CheckValidWallpaperSelected(true) == false)
				return;

			ChangeWallpaper(true, false);
		}

		private void startButton_Click(object sender, EventArgs e)
		{
			if (CheckValidWallpaperSelected(true) == false)
				return;

			int interval = 0;

			if(int.TryParse(intervalTextBox.Text, out interval) == false)
			{
				SafeMessageBox(Resource.InputTimeInterval, Resource.MessageBoxTitle);

				return;
			}

			if(interval < 1)
			{
				SafeMessageBox(Resource.ValueMustGreaterThanOne, Resource.MessageBoxTitle);

				return;
			}

			interval = interval * 60 * 1000;

			changeCycleTimer.Interval = interval;
			changeCycleTimer.Start();

			ChangeWallpaper(true, false);

			EnableControls(false);
		}

		private void changeCycleTimer_Tick(object sender, EventArgs e)
		{
			ChangeWallpaper(true, false);
		}

		private void cancelButton_Click(object sender, EventArgs e)
		{
			changeCycleTimer.Stop();

			EnableControls(true);
		}

		private void screenComboBox_SelectedIndexChanged(object sender, EventArgs e)
		{
			SelectPreviewScreen(screenComboBox.SelectedIndex);
		}

		private bool IsStarted()
		{
			return !startButton.Enabled;
		}

		private void randomCheckBox_CheckedChanged(object sender, EventArgs e)
		{
			CurrentScreen.random = randomCheckBox.Checked;

			if(!IsStarted())
				intervalTextBox.Enabled = randomCheckBox.Checked;

			previewBox.Invalidate();
		}

		private void runWhenWinStartCheckBox_CheckedChanged(object sender, EventArgs e)
		{
			if (startupCheckBox.Checked)
			{
				WindowsStartup.AddStartUp();
			}
			else
			{
				WindowsStartup.RemoveStartUp();
			}
		}

		private void previewBox_MouseDown(object sender, MouseEventArgs e)
		{
			Point point = e.Location;

			point.X -= drawOrigin.X;
			point.Y -= drawOrigin.Y;

			for (int i = 0; i < screens.Length; i++)
			{
				Rectangle rect = screens[i].previewRect;

				if (rect.Contains(point))
				{
					SelectPreviewScreen(i);

					screenComboBox.SelectedIndex = i;

					break;
				}
			}
		}

		private void SelectPreviewScreen(int i)
		{
			selectedScreenIndex = i;

			// update image list (exactly matching size)
			UpdateImageListBox();

			randomCheckBox.Checked = CurrentScreen.random;

			if (!IsStarted())
				intervalTextBox.Enabled = randomCheckBox.Checked;

			previewBox.Invalidate();
		}

		private void ChangeWallpaper(bool notifyOnError, bool keepCurrentImage)
		{
			if (CheckValidWallpaperSelected(notifyOnError) == false)
				return;

			wallpaperChanger.ChangeWallpaper(keepCurrentImage);

			SaveConfig();

			GC.Collect();
		}

		void EnableControls(bool flag)
		{
			Control[] controls = {
				folderBrowseButton, 
				imageListBox, 

				startupCheckBox,
				changeWhenRunCheckBox,

				intervalTextBox,

				randomCheckBox,
				startButton
			};

			foreach (Control control in controls)
			{
				control.Enabled = flag;
			}
		}

		private void OpenImageFolder(string path)
		{
			imageFolderTextBox.Text = path;

			CreateImageItemList(path, new List<string>(ImageExtensions));

			ResetItems();

			// make proper image list
			foreach(ScreenInformation screen in screens)
			{
				screen.imageItemList = new List<ImageItem>();

				int screenWidth = screen.bounds.Width;
				int screenHeight = screen.bounds.Height;

				screen.imageItemList = GetImageItemListBySize(screenWidth, screenHeight);
			}

			// update image list box
			UpdateImageListBox();
		}

		private void UpdateImageSizeLabel(int width, int height)
		{
			imageSizeLabel.Text = string.Format("{0} X {1}", width, height);
		}

		private void CreateImageItemList(string path, List<string> ImageExtensions)
		{
			if (!Directory.Exists(path))
			{
				SafeMessageBox(Resource.InvalidDirectory, Resource.MessageBoxTitle);

				return;
			}

			imageItemList = new List<ImageItem>();

			DirectoryInfo di = new DirectoryInfo(path);
			FileInfo[] files = di.GetFiles();

			foreach (FileInfo file in files)
			{
				if (ImageExtensions.Contains(file.Extension))
				{
					imageItemList.Add(new ImageItem(file.Name, file.FullName));
				}
			}
		}

		private List<ImageItem> GetImageItemListBySize(int width, int height)
		{
			List<ImageItem> list = new List<ImageItem>();

			if (imageItemList != null)
			{
				foreach (ImageItem item in imageItemList)
				{
					if (item.Width == width && item.Height == height)
					{
						// add this member
						list.Add(item);
					}
				}
			}

			return list;
		}

		/// <summary>
		/// 스크린 선택에 의해 호출됨
		/// </summary>
		private void UpdateImageListBox()
		{
			// 참고: datasource를 사용하면 selectedindex change이벤트가 발생함.
			imageListBox.Items.Clear();

			if (CurrentScreen.imageItemList == null)
				return;

			foreach (ImageItem item in CurrentScreen.imageItemList)
			{
				imageListBox.Items.Add(item);
			}

			// select item
			imageListBox.SelectedIndex = CurrentScreen.selectedImageIndex;
		}

		private void openToolStripMenuItem_Click(object sender, EventArgs e)
		{
			ShowForm();
		}

		private void hideToolStripMenuItem_Click(object sender, EventArgs e)
		{
			Visible = false;
		}

		private void exitToolStripMenuItem_Click(object sender, EventArgs e)
		{
			Close();
		}

		private void programInfoToolStripMenuItem_Click(object sender, EventArgs e)
		{
			AboutForm form = new AboutForm();

			form.ShowDialog();
		}

		private void MultiWallpaper_HelpButtonClicked(object sender, CancelEventArgs e)
		{
			AboutForm form = new AboutForm();

			form.ShowDialog();

			e.Cancel = true;
		}

		private void notifyIcon_MouseClick(object sender, MouseEventArgs e)
		{
			if (e.Button == MouseButtons.Left)
			{
				ShowForm();
			}
		}

		private static void DrawQuestionMark(Graphics g, Rectangle rect)
		{
			const int FontSize = 72;

			GraphicsPath path = new GraphicsPath();
			FontFamily font = new FontFamily("Arial");

			path.AddString("?", font, (int)FontStyle.Regular, FontSize, new Point(0, 0), StringFormat.GenericTypographic);

			// get path rect
			float right = 0, bottom = 0;

			foreach (PointF pt in path.PathPoints)
			{
				if (pt.X > right)
					right = pt.X;

				if (pt.Y > bottom)
					bottom = pt.Y;
			}

			int width = (int)right;
			int height = (int)bottom;

			// translate
			Matrix translate = new Matrix();

			int pathX = rect.X + (rect.Width - width) / 2;
			int pathY = rect.Y + (rect.Height - height) / 2;

			translate.Translate(pathX, pathY);

			path.Transform(translate);

			// draw path
			Pen pen = new Pen(Brushes.Black, 3);

			g.SmoothingMode = SmoothingMode.AntiAlias;

			g.DrawPath(pen, path);
			g.FillPath(Brushes.White, path);
		}

		private void previewBox_Paint(object sender, PaintEventArgs e)
		{
			const int HighlightBorder = 3;
			const int FrameMargin = 5;
			const int WhiteRegionMargin = 3;

			Graphics g = e.Graphics;

			g.TranslateTransform(drawOrigin.X, drawOrigin.Y);

			// highlight current selected screen
			Rectangle highlightRect = CurrentScreen.previewRect;

			Pen highlightPen = new Pen(Color.Blue, HighlightBorder);
			highlightPen.Alignment = PenAlignment.Inset;

			g.DrawRectangle(highlightPen, highlightRect.X, highlightRect.Y, highlightRect.Width, highlightRect.Height);

			for (int i = 0; i < screens.Length; i++)
			{
				Rectangle rect = screens[i].previewRect;

				// draw frame
				rect.Inflate(-FrameMargin, -FrameMargin);

				g.DrawRectangle(Pens.Black, rect.X, rect.Y, rect.Width, rect.Height);

				// draw white region
				rect.Inflate(-WhiteRegionMargin, -WhiteRegionMargin);

				g.FillRectangle(Brushes.White, rect.X, rect.Y, rect.Width, rect.Height);

				// set image quality
				g.InterpolationMode = InterpolationMode.High;

				// draw preview image
				if (screens[i].selectedThumbnail != null)
					g.DrawImage(screens[i].selectedThumbnail, rect);

				// draw random mark
				if (screens[i].random)
				{
					// draw alpha rect
					SolidBrush alphaBrush = new SolidBrush(Color.FromArgb(192, 255, 255, 255));

					g.FillRectangle(alphaBrush, rect);

					DrawQuestionMark(g, rect);
				}
			}
		}

		protected override void WndProc(ref Message m)
		{
			const int WM_SETTINGCHANGE = 0x1A;
			const int WM_SYSCOMMAND = 0x112;
			const int SC_MINIMIZE = 0xF020;
			const int SC_RESTORE = 0xF120;

			switch (m.Msg)
			{
				// 최소화 버튼
				case WM_SYSCOMMAND:
					if ((int)m.WParam == SC_MINIMIZE)
					{
						Hide();

						return;
					}
					break;

				// 시스템 설정 변경
				case WM_SETTINGCHANGE:
					if ((int)m.WParam == WindowsAPI.SPI_SETWORKAREA)
					{
						Init();

						ChangeWallpaper(false, true);
					}
					break;
			}

			base.WndProc(ref m);
		}

		// config
		private void SaveConfig()
		{
			GeneralConfigSection generalSection = new GeneralConfigSection();
			ScreenConfigSection[] screenSections = new ScreenConfigSection[screens.Length];

			int interval = 0;

			int.TryParse(intervalTextBox.Text, out interval);

			generalSection.FolderPath = imageFolderTextBox.Text;
			generalSection.NumScreens = screens.Length;
			generalSection.Interval = interval;
			generalSection.Startup = startupCheckBox.Checked;
			generalSection.ChangeWhenRun = changeWhenRunCheckBox.Checked;

			for (int i = 0; i < screens.Length; i++)
			{
				ScreenInformation screen = screens[i];
				screenSections[i] = new ScreenConfigSection();

				// image file name
				string imageFileName = "";

				if (screen.selectedImageIndex != -1)
					imageFileName = screen.imageItemList[screen.selectedImageIndex].FileName;

				screenSections[i].ImageFileName = imageFileName;
				screenSections[i].Random = screens[i].random;
			}

			Config.Save(generalSection, screenSections);
		}

		private void LoadConfig()
		{
			GeneralConfigSection generalSection;
			ScreenConfigSection[] screenSections;

			if (Config.Load(out generalSection, out screenSections))
			{
				// folder path
				if (generalSection.FolderPath != "")
					OpenImageFolder(generalSection.FolderPath);

				// interval
				intervalTextBox.Text = generalSection.Interval.ToString();

				// startup
				startupCheckBox.Checked = generalSection.Startup;

				// change when run
				changeWhenRunCheckBox.Checked = generalSection.ChangeWhenRun;

				// determine num of screen (min value) 저장된 컨픽과 현재 설정이 다를 수 있기 때문.
				int numOfScreens = Math.Min(screens.Length, screenSections.Length);

				for (int screenIndex = 0; screenIndex < numOfScreens; screenIndex++)
				{
					ScreenInformation screen = screens[screenIndex];
					ScreenConfigSection screenSection = screenSections[screenIndex];

					// find image file name and get index
					List<ImageItem> list = screen.imageItemList;
					string imageFileName = screenSection.ImageFileName;

					for (int i = 0; i < list.Count; i++)
					{
						if (imageFileName == list[i].FileName)
						{
							// set index and image
							screen.selectedImageIndex = i;

							screen.selectedThumbnail = list[i].Thumbnail;
							screen.selectedImageFilePath = list[i].FilePath;
						}
					}

					screen.random = screenSection.Random;
				}

				// update ui
				imageListBox.SelectedIndex = CurrentScreen.selectedImageIndex;

				previewBox.Invalidate();
			}
			else
			{
				// load default setting
				intervalTextBox.Text = DefaultTimeInterval.ToString();
			}
		}

		/// <summary>
		/// 타이머에 의해 메시지박스가 여러개 뜨는것을 방지하기 위해 메시지박스가 필요할 땐 이 함수를 사용
		/// </summary>
		/// <param name="text"></param>
		/// <param name="caption"></param>
		/// <returns></returns>
		private DialogResult SafeMessageBox(string text, string caption, MessageBoxButtons buttons)
		{
			DialogResult result;

			if(changeCycleTimer.Enabled)
			{
				changeCycleTimer.Stop();

				result = MessageBox.Show(text, caption, buttons);

				changeCycleTimer.Start();
			}
			else
			{
				result = MessageBox.Show(text, caption, buttons);
			}

			return result;
		}

		private DialogResult SafeMessageBox(string text, string caption)
		{
			return SafeMessageBox(text, caption, MessageBoxButtons.OK);
		}
	}	
}